﻿package joint;


import core.*;
import cont.*;
import mess.*;
import scrn.*;
import media.*;

import java.io.*;


// ================================ Класс управления приемом/отправкой сообщений
public final class IOMan extends Thread implements TickEvent {

// =================================================================== Константы

private final int BLK_SIZE = 512; // Размер считываемого текстового фрагмента

// Паузы
public final int P_HIST = 50; // Пауза при приеме истории
public final int P_WORK = 1000; // Пауза в рабочем режиме
private boolean fPause = true; // Флаг паузы
public int timeSleep = P_HIST; // Текущее значение паузы

private Tick kaTick; // Таймер вызова функции проверки наличия связи
private int kaPeriod = (4 * 60 + 50) * 1000; // Период проверки наличия связи, мс

// Соединение
private IO io; // Объект ввода/вывода
public boolean fOnline = false; // В сети


// Другое
private boolean fRun = true; // Флаг времени жизни
public String status = ""; // Статусная строка


// =============================================================== Инициализация
public IOMan() {

	Mess.m("IOMan.init", "=====");
	this.start();
} // Конструктор


// ======================================================================= Пауза
public void pause() {

	synchronized (this) {
		fPause = true;
	}
}


// =============================================================== Возобновление
public void resumeThr() {

	Mess.m("IOMan.resume", "Продолжение работы менеджера");
	synchronized (this) {
		if (io != null) {
			fPause = false;
			notifyAll();
		}
	}
}


// ================================================================= Соединиться
public void connect() {

	disconnect(Mess.LOCAL, "Подготовка к соединению");
	io = new IO();
}


// =============================================================== Разъединиться
public void disconnect(int from, String why) {

	Mess.m("IOMan.disconnect", "Разъединяюсь: " + why + " from =" + from);
	try {
		pause();

		if (kaTick != null) {
			kaTick.exit();
			kaTick = null;
		}

		if (io != null) {
			if (from >= Mess.LOCAL) {
				io.close();
			}
			io = null;
		}

		if (from <= Mess.LOCAL) {
			setStatus("Не в сети. " + why);
			setOnline(false);
		}
	} catch (Exception e) {
		Mess
				.err("IOMan.disconnect", "Ошибка при разъединении по причине " + why, e);
	}
}


// ===================================================================== СОБЫТИЯ

// ==================================================================== Соединен
public void connConnected() {

	Mess.m("IOMan.connConnected", "Соединен");

	try {
		setStatus("Соединен");

		kaTick = new Tick(this, 768, kaPeriod, "IOManT");
		kaTick.resumeTick();

		outLogin();

		if (Setting.camImgEnc == null) {
			byte[] setBytes = Setting.toBytes(new int[] { Setting.CAM_IMG_ABIL,
					Setting.DISP_W, Setting.DISP_H });
			outSetting(setBytes);
		}

	} catch (Exception e) {
		Mess.err("IOMan.connConnected", e);
		disconnect(Mess.LOCAL, "Подключиться не удалось");
	}
}


// ====================================================== Соединиться не удалось
public void connNotConnected(String errorText, Exception exception) {

	Mess.m("IOMan.connNotConnected", errorText, exception);
	Man.scrMan.showAlert("Не в сети", errorText, 0, true);
	disconnect(Mess.LOCAL, errorText);
}


// ============================================================= ПРИЕМ СООБЩЕНИЙ
// ================================================== Прочитать список контактов
private void inContList() throws IOException {

	int len = io.readInt(); // Общая длина
	io.startRead();
	int contType = io.readInt(); // Тип списка контактов
	int add = io.readInt(); // Постоянство или номер комнаты
	int num = io.readInt(); // Кол-во полученных результатов

	Mess.m("IOMan.inContList", "> Получен список. Длина=" + len + ", тип="
			+ contType + ", постоянство=" + add + ", кол-во=" + num);

	switch (contType) {
		case Cont.ONE:
			if (num > 0) {
				ContOne[] ones = new ContOne[num];
				for (int q = 0; q < num; ++q) {
					int infoLen = io.readInt();
					ones[q] = new ContOne(io.readBytes(infoLen));
				}

				// Сообщить модулям
				if (add > 0) { // добавляются в список
					if (ones.length == 1) {
						Man.contMan.addCont(Cont.ONE, ones[0], Cont.M_IO);
					} else {
						Man.contMan.setList(Cont.ONE, ones);
					}
					Man.scrMan.showMain();
				} else { // НЕ добавляется в список
					Man.scrMan.inFind(Cont.ONE, ones);
				}

			}
			break;

		case Cont.ROOM:
			if (num > 0) {
				ContRoom[] rooms = new ContRoom[num];
				for (int q = 0; q < num; ++q) {
					int infoLen = io.readInt();
					rooms[q] = new ContRoom(io.readBytes(infoLen));
				}

				// Сообщить модулям
				if (add > 0) {
					if (rooms.length == 1) {
						// TODO Настроить contMan
						Man.contMan.addCont(Cont.ROOM, rooms[0], Cont.M_IO);
					} else {
						Man.contMan.setList(Cont.ROOM, rooms);
					}
					Man.scrMan.showMain();
				} else { // НЕ добавляется в список
					Man.scrMan.inFind(Cont.ROOM, rooms);
				}


			}
			break;

		case Cont.IN_ROOM:

			if (num > 0) {
				ContOne[] ones = new ContOne[num];
				for (int q = 0; q < num; ++q) {
					int infoLen = io.readInt();
					ones[q] = new ContOne(io.readBytes(infoLen));
				}

				Man.scrMan.inRoomWho(add, ones);
			}
			break;
		default:
			Mess.err("IOMan.inContList", "Неизвестный тип контакта - " + contType);
	}

}


// ============================================================= Удалить контакт
public void inDelCont() throws IOException {

	int contType = io.readInt();
	int contNum = io.readInt();

	Mess.m("IOMan.inDelCont", "Удалить контакт, тип=" + contType + ", №"
			+ contNum);
	String name = Man.contMan.delCont(contType, contNum);
	Man.scrMan.showAlert("Удаление контакта", name + " удалил свой приват.", 5,
			false);
}


// ========================================================== Получены настройки
private void inSetting() throws IOException {

	int len = io.readInt();
	Mess.m("IOMan.inSetting", "Получены настройки. Длина=" + len);

	if (len > 0) {
		Setting.fromBytes(io.readBytes(len));
	}
}


// ================================================== Текстовое личное сообщение
private void inTextOne() {

	byte[] buf = new byte[BLK_SIZE]; // Буфер текста сообщения
	int fromNum = 0; // Номер контакта
	int messLen; // Длина сообщения
	int messNum = -1; // Номер сообщения

	MessOne mess = new MessOne(); // Сообщение

	mess.color = OneCol.T_IN_ONE; // Тип сообщения

	try {
		messLen = io.readInt();
		io.startRead();

		mess.h = io.readUnsByte(); // Читаем часы и флаги
		mess.m = io.readUnsByte(); // Читаем минуты

		int histState = (mess.h & 0xc0) >> 6; // Состояние истории

		if (histState == 0) { // Не история
			this.timeSleep = P_WORK;
		} else { // История
			this.timeSleep = P_HIST;
			mess.day = io.readInt();
			mess.mon = io.readInt();
			mess.year = io.readInt();
		}

		switch (histState) {
			case 0:
				// Не история, обычное
				break;
			case 1:
				mess.color = OneCol.T_IN_ONE;
				break;
			case 2:
				mess.color = OneCol.T_OUT_SERV;
				break;
			case 3:
				mess.color = OneCol.T_OUT_ADR;
				break;
			default:
				Mess.err("IOMan.inTextOne", "Состояние истории: " + histState);
		}
		if ((mess.h & Mat.BIT5) != 0) {
			messNum = io.readInt(); // Читаем номер сообщения
		}
		mess.h &= 0x1f;
		fromNum = io.readInt(); // Читаем номер контакта

		messLen -= io.getReadLen(); // Вычисляем оставшуюся длину

		// Читаем текст
		while (messLen > BLK_SIZE) {
			io.readBytes(buf, 0, BLK_SIZE);
			mess.text += Mat.iso2utf(buf, BLK_SIZE);
			messLen -= BLK_SIZE;
		}
		io.readBytes(buf, 0, messLen);
		if (Setting.fRes && (messNum >= 0)) {
			outResOne(fromNum, messNum);
		}
		mess.text += Mat.iso2utf(buf, messLen);
		Mess.m("IOMan.inTextOne", "> от N=" + fromNum + " messNum=" + messNum + " "
				+ messLen + " байт, " + mess);

		// Коррекция часов
		mess.h += Setting.timeSerShift;
		if (mess.h >= 24) {
			mess.h -= 24;
		}


		// Сообщить остальным модулям
		if (mess.year <= 0) { // Не история
			Man.soundMan.play(MediaType.S_MESS_ONE);
		}

		Man.contMan.addTextOne(fromNum, mess);
		Man.scrMan.inText(Cont.ONE, fromNum, mess);

	} catch (Exception e) {
		Mess.err("IOMan.inTextOne", "Не могу прочитать сообщение", e);
	}
}


// ======================================================== Сообщение из комнаты
// ========================================================= Текстовое в комнату
private void inTextRoom() {

	int f = 0; // Флаги
	int messLen; // Длина сообщения
	int messNum = -1; // Номер сообщения
	int roomNum = -1; // Номер комнаты

	MessRoom messRoom = new MessRoom(); // Сообщение
	messRoom.color = RoomCol.T_IN_ROOM;

	byte[] buf = new byte[BLK_SIZE]; // Буфер текста сообщения

	try {
		messLen = io.readInt();
		io.startRead();

		f = io.readUnsByte();

		messRoom.h = io.readUnsByte(); // Читаем часы
		messRoom.m = io.readUnsByte(); // Читаем минуты

		if ((f & Mat.BIT6) == 0) {
			this.timeSleep = P_WORK;
		} else { // Читаем дату
			this.timeSleep = P_HIST;
			messRoom.day = io.readInt();
			messRoom.mon = io.readInt();
			messRoom.year = io.readInt();
		}

		roomNum = io.readInt(); // Номер комнаты

		messRoom.fromType = (f & (Mat.BIT3 | Mat.BIT4)) >> 3;
		switch (messRoom.fromType) { // Тип отправившего контакта

//			case MessRoom.FROM_SERVER:
//				messRoom.color = RoomCol.T_IN_SERV;
//				break;

			case MessRoom.FROM_ME:
				messRoom.color = RoomCol.T_OUT_ADR;
				break;

			case MessRoom.FROM_ONE:
			case MessRoom.FROM_CACHE_OLD:
				// Только номер
				messRoom.fromNum = io.readInt();
				break;

			case MessRoom.FROM_CACHE_NEW:
				// Номер и ник
				messRoom.fromNum = io.readInt();
				messRoom.fromCache = io.readString();
				Man.contMan.addCache(messRoom.fromNum, messRoom.fromCache);
				messRoom.fromType = MessRoom.FROM_CACHE_OLD;
				break;

			default:
				Mess.err("IOMan.inTextRoom", "Неизвестный тип отправителя: "
						+ messRoom.fromType);
		}

		messRoom.tarType = f & 0x07;
		switch (messRoom.tarType) { // Тип получающего контакта

			case MessRoom.TAR_ME:
				messRoom.fPrv = true;
				messRoom.color = RoomCol.T_IN_PRIVATE;
				break;

			case MessRoom.TAR_ME_ALL:
			case MessRoom.TAR_ALL:
				break;

			case MessRoom.TAR_ONE:
			case MessRoom.TAR_CACHE_OLD:
				messRoom.tarNum = io.readInt();
				break;

			case MessRoom.TAR_CACHE_NEW:
				messRoom.tarNum = io.readInt();
				messRoom.tarCache = io.readString();
				Man.contMan.addCache(messRoom.tarNum, messRoom.tarCache);
				messRoom.tarType = MessRoom.TAR_CACHE_OLD;
				break;

			default:
				Mess.err("PhMan.outTextRoom", "Неизвестный тип получателя: "
						+ messRoom.tarType);
		}

		if ((f & Mat.BIT5) != 0) {
			messNum = io.readInt();
		}

		messLen -= io.getReadLen(); // Вычисляем оставшуюся длину

		// Читаем текст
		while (messLen > BLK_SIZE) {
			io.readBytes(buf, 0, BLK_SIZE);
			messRoom.text += Mat.iso2utf(buf, BLK_SIZE);
			messLen -= BLK_SIZE;
		}
		io.readBytes(buf, 0, messLen);
		messRoom.text += Mat.iso2utf(buf, messLen);

		// Чтение сообщения завершено

		if (Setting.fRes && (messNum >= 0)) {
			//			outResOne(fromNum, messNum); TODO Отправить подтверждение доставки при необходимости
		}

		// Коррекция часов
		messRoom.h += Setting.timeSerShift;
		if (messRoom.h >= 24) {
			messRoom.h -= 24;
		}

		Mess.m("IOMan.inTextRoom", "> " + messRoom);

		// Передать внешним модулям

		if (messRoom.year <= 0) { // Не история
			Man.soundMan.play(MediaType.S_MESS_ROOM);
		}

		Man.contMan.addTextRoom(roomNum, messRoom);
		Man.scrMan.inText(Cont.ROOM, roomNum, messRoom);

	} catch (Exception e) {
		Mess.err("IOMan.inTextRoom", "Не могу прочитать сообщение", e);
	}
}


// ============================================================= Статус контакта
private void inStatOne() throws IOException {

	StatOne statOne = new StatOne();

	statOne.unserialize(io.readBytes(new byte[io.readInt()]));

	Mess.m("IOMan.inStatOne", "Получен статус: " + statOne);

	Man.soundMan.play(MediaType.S_CONNECT);
	Man.contMan.setStat(statOne);
	// scrMan.refresh(Cont.ONE, statOne.fromNum);
}


//=============================================================== Статус комнаты
private void inStatRoom() throws IOException {

	int len = io.readInt(); // Длина
	io.startRead();

	int roomNum = io.readInt(); // Номер комнаты

	len -= io.getReadLen();
	byte[] buf = io.readBytes(len); // Состояние комнаты


	Mess.m("IOMan.inStatRoom", "Получен статус комнаты №" + roomNum + " длина="
			+ len);

	this.setStatus(Man.contMan.inStatRoom(roomNum, buf));
}


// ============================================================== На связи время
private int inKeepAliveTime() {

	Mess.m("IOMan.inKeepAliveTime", ">");
	setStatus("В сети");
	return 0;
}


// ====================================================== Подтверждение доставки
private void inResOne() throws IOException {

	int contNum = io.readInt();
	int messNum = io.readInt();
	Mess.m("IOMan.inResOne", "> Доставлено контакту №" + contNum + " сообщение №"
			+ messNum);

	// Сообщить модулям
	Man.contMan.inResOne(contNum, messNum);
	Man.scrMan.inRes(Cont.ONE, contNum);
}


//============================================= Подтверждение доставки в комнату
private void inResRoom() throws IOException {

	int roomNum = io.readInt();
	int messNum = io.readInt();
	Mess.m("IOMan.inResRoom", "> Доставлено в комнату N=" + roomNum
			+ " сообщение №" + messNum);

	// Сообщить модулям
	Man.contMan.inResRoom(roomNum, messNum);
	Man.scrMan.inRes(Cont.ROOM, roomNum);
}


// ================================================================== От сервера
private void inServ() throws IOException {

	String text;
	int len = io.readInt();
	io.startRead();
	int sec = io.readInt();

	text = Mat.iso2utf(io.readBytes(new byte[len - io.getReadLen()]));
	Mess.m("IOMan.inserv", "> Сервер (" + len + " байт), " + sec + "сек.: "
			+ text);

	// scrMan.vibra(Setting.vibraMessTime);
	Man.scrMan.showAlert("Сервер:", text, sec, true);
}


// ========================================================== ОТПРАВКА СООБЩЕНИЙ
// ==================================================================== На связи
public void outKeepAlive() throws IOException {

	Mess.m("IOMan.outKeepAlive", "< KeepAlive");
	io.start(ComType.mKeepAlive);
	io.flush();
	kaTick.update();
}


//===================================================== Передача идентификаторов
private void outLogin() {

	Mess.m("IOMan.outLogin", "< Передача идентификаторов");

	try {
		InputStream is = this.getClass().getResourceAsStream("/res/empty.gif");
		byte[] b = new byte[16];
		is.read(b);
		is.close();

		io.start(Setting.TYPE);
		// Версия
		io
				.sendBytes(new byte[] {
						(byte) ((Integer.parseInt(Setting.VER.substring(0, 2))) & 0xff),
						(byte) ((Integer.parseInt(Setting.VER.substring(3, 5))) & 0xff),
						(byte) ((Integer.parseInt(Setting.VER.substring(6, 10)) - 2000) & 0xff) });


		// Идентификатор
		io.sendInt(ComType.mID);
		io.sendBytes(b);

		// Логин и пароль
		io.sendInt(ComType.vLogin);
		io.sendString(Setting.login);
		io.sendString(Setting.password);

		// Контрольные суммы CRC
		int crc = -1;

		for (int q = Cont.ROOM; q <= Cont.ONE; ++q) {
			crc = Man.contMan.getCRC(q);
			io.sendInt(ComType.vCRC);
			io.sendInt(q);
			io.sendInt(crc);
		}

		io.flush();

		kaTick.update();

		setOnline(true);
		resumeThr();

	} catch (Exception e) {
		Mess.err("IOMan.outLogin", "Ошибка передачи идентификаторов", e);
	}

}


// ================================================== Добавить найденный контакт
public void outAddCont(int contType, int contNum) throws IOException {

	Mess.m("IOMan.outAddCont", "< Тип " + contType + ", номер " + contNum);
	io.start(ComType.vAddCont);
	io.sendInt(contType);
	io.sendInt(contNum);
	io.flush();
	kaTick.update();
}


// =============================================================== Отправить CRC
/*
 * public void outCRC(int contType, int CRC) { Mess.m("IOMan.outCRC",
 * "< Отправка CRC. Тип=" + contType + ", CRC=" + CRC); io.start(ComType.vCRC);
 * io.sendInt(contType); io.sendInt(CRC); io.flush(); kaTick.update(); }/
 */


// ============================================================= Удалить контакт
public void outDelCont(int contType, int contNum) throws IOException {

	io.start(ComType.vDelCont);
	io.sendInt(contType);
	io.sendInt(contNum);
	io.flush();
	kaTick.update();
}


// ================================================= Найти контакт с параметрами
public void outFindOne(String name, int maxResults) throws IOException {

	Mess.m("IOMan.outFindOne", "< Запрос поиска контакта " + name);
	io.start(ComType.vFindOne);
	io.sendLen();
	io.sendInt(maxResults);
	io.sendInt(OneInfo.cLogin);
	io.sendInt(name.length());
	io.sendBytes(Mat.utf2iso(name));
	// Сюда добавить остальные критерии поиска
	io.flush();
	kaTick.update();
}


// ================================================= Найти комнату с параметрами
public void outFindRoom(String name, int maxResults) throws IOException {

	Mess.m("IOMan.outFindRoom", "< Запрос поиска комнаты " + name);
	io.start(ComType.vFindRoom);
	io.sendLen();
	io.sendInt(maxResults);
	io.sendInt(RoomInfo.NAME);
	io.sendInt(name.length());
	io.sendBytes(Mat.utf2iso(name));
	// Сюда добавить остальные критерии поиска
	io.flush();
	kaTick.update();
}


// ====================================================== Подтверждение доставки
private void outResOne(int contNum, int messNum) throws IOException {

	io.start(ComType.vResOne);
	io.sendInt(contNum);
	io.sendInt(messNum);
	io.flush();
	kaTick.update();
}


//======================================= Запрос списка присутствующих в комнате
public void outRoomWho(int roomNum) throws IOException {

	Mess.m("IOMan.outRoomWho", "< Запрос списка контактов комнаты " + roomNum);
	io.start(ComType.vRoomWho);
	io.sendInt(roomNum);
	io.flush();
	kaTick.update();
}


// ============================================================ Текстовое личное
public void outTextOne(MessOne messOne, int tarOneNum, int messNum)
		throws IOException {

	Mess.m("IOMan.outTextOne", "< " + messOne);
	io.start(ComType.vTextOne);
	io.sendLen();
	if (Setting.fRes) {
		io.sendInt(messNum + 1);
	}
	io.sendInt(tarOneNum);
	io.sendBytes(Mat.utf2iso(messOne.text));
	io.flush(); /**/
	kaTick.update();
}


// =========================================================== Текстовое комната
public void outTextRoom(int tarRoomNum, int messNum, MessRoom mess)
		throws IOException {

	Mess.m("IOMan.outTextRoom", "< В комн=" + tarRoomNum + ", messNum=" + messNum
			+ ", сообщение: " + mess);
	int f = 0;

	f |= mess.tarType & 0x03;

	if (mess.fPrv) {
		f |= Mat.BIT3;
	}

	if ((messNum >= 0) && (Setting.fRes)) {
		f |= Mat.BIT2;
	}

	io.start(ComType.vTextRoom);
	io.sendLen();
	io.sendByte((byte) f);
	io.sendInt(tarRoomNum);

	switch (mess.tarType) {
		case MessRoom.TAR_CACHE_OLD:
		case MessRoom.TAR_ONE:
		case MessRoom.TAR_IN_ROOM:
			io.sendInt(mess.tarNum);
	}

	if ((f & Mat.BIT2) != 0) {
		io.sendInt(messNum);
	}
	io.sendBytes(Mat.utf2iso(mess.text));
	io.flush();
	kaTick.update();
}


//===================================================== Передача идентификаторов
private void outSetting(byte[] bytes) throws IOException {

	Mess.m("IOMan.run", "> Настройки");
	io.start(ComType.vSetting);
	io.sendLen();
	io.sendBytes(bytes);
	io.flush();
	kaTick.update();
}


// ================================================================ Среда приема
@Override
public void run() {

	Thread.currentThread().setName("ioMan");
	Mess.m("IOMan.run", "Старт приема сообщений");

	int messType = 0; // Тип сообщения

	while (fRun) {
		try {
			synchronized (this) {
				while (fRun && fPause) {
					this.wait(); // Произвольный интервал ожидания для исключения полного зависания потока
				}
			}

			// Цикл чтения сообщения
			kaTick.update();
			sleep(timeSleep); // Страховка от перегрузки спамом

			// Читаем заголовок

			messType = io.readInt();

			Mess.m("IOMan.run", "> Входящее. Тип " + messType);

			// ------------------------------------- Обработка типов
			switch (messType) {
				case ComType.mKeepAlive: // Сигнал поддержания связи
					setStatus("В сети");
					break;
				case ComType.mKeepAliveTime:
					inKeepAliveTime();
					break;
				case ComType.vContList: // Список контактов
					inContList();
					break;
				case ComType.vResOne: // Подтверждение доставки
					inResOne();
					break;
				case ComType.vResRoom: // Подтверждение доставки в комнату
					inResRoom();
					break;
				case ComType.vServ:
					inServ();
					break;
				case ComType.vStatOne: // Статус контакта
					inStatOne();
					break;
				case ComType.vStatRoom: // Статус комнаты
					inStatRoom();
					break;
				case ComType.vTextOne:
					inTextOne();
					break;
				case ComType.vTextRoom:
					inTextRoom();
					break;
				case ComType.vDelCont: // Контакт удален !c 2010.01.12!
					inDelCont();
					break;
				case ComType.vSetting:
					inSetting();
					break;
				case ComType.mBye: // Завершить сеанс
				case -1: // Чтение прервано пользователем
					disconnect(Mess.LOCAL, "Связь потеряна");
					break;
				default:
					Mess.err("IOMan.run", "> Неизвестный тип сообщения: " + messType);
					io.readBytes(io.readInt()); // Читаем длину и содержимое без обработки
					break;
			} // Конец обработки команд
			//			Mess.m("IOMan.run", "Отработано. Тип " + messType);

		} catch (InterruptedIOException e) {
			Mess.m("IOMan.run", "Связь прервана");
		} catch (IOException e) {
			disconnect(Mess.LOCAL, "Связь потеряна");
			Mess.err("IOMan.run", e);
		} catch (InterruptedException e) {
			Mess.m("IOMan.run", "Прерваны");
		} catch (Exception e) {
			Mess.err("IOMan.run", "Ошибка приема", e);
			disconnect(Mess.LOCAL, "Ошибка приема");
		}
	} // Конец основоного цикла приема
	/**/
	Mess.m("IOMan.run", "Exit thread");
}


// ======================================= Задать состояние "в сети / не в сети"
private void setOnline(boolean fOnline) {

	this.fOnline = fOnline;

	Man.contMan.fOnline(fOnline);
	Man.scrMan.fOnline(fOnline);
}


// ======================================================== Задать статус-строку
private void setStatus(String statusText) {

	status = statusText;

	Man.scrMan.chStat(statusText);
}


// ===================================================== Проверить наличие связи
public void tick(int num) {

	setStatus("Сеть временно недоступна");
	try {
		outKeepAlive();
	} catch (Exception e) {
		Mess.err("IOMan.tick", "Не могу отправить сигнал `на связи`", e);
	}
}


// ======================================================================= Выход
public void exit(boolean fOk, int from) {

	if (fOk && (io != null)) {
		try {
			io.start(ComType.mBye);
			io.flush();
		} catch (Exception e) {
		}
	}

	fRun = false;
	fPause = false;

	disconnect(from, "Работа программы завершена");
	synchronized (this) {
		notifyAll();
	}
	io = null;
	kaTick = null;

}

}
